package com.jzoom.zoom.admin.models;

import com.jzoom.zoom.admin.entities.DecoTableVo;
import com.jzoom.zoom.admin.utils.AdminUtils;
import com.jzoom.zoom.caster.Caster;
import com.jzoom.zoom.common.expression.Symbol;
import com.jzoom.zoom.common.utils.Classes;
import com.jzoom.zoom.common.utils.DatetimeUtils;
import com.jzoom.zoom.dao.Ar;
import com.jzoom.zoom.dao.Dao;
import com.jzoom.zoom.dao.Page;
import com.jzoom.zoom.dao.Record;
import com.jzoom.zoom.dao.SqlBuilder.Like;
import com.jzoom.zoom.dao.SqlBuilder.Sort;
import com.jzoom.zoom.dao.adapters.NameAdapter;
import com.jzoom.zoom.dao.impl.ZoomDao;
import com.jzoom.zoom.dao.meta.ColumnMeta;
import com.jzoom.zoom.dao.meta.TableMeta;
import com.jzoom.zoom.excel.ExcelTable;
import com.jzoom.zoom.web.exception.StatusException;
import com.jzoom.zoom.web.view.View;
import com.jzoom.zoom.web.view.impl.OutputStreamView;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;


public class AdminDaoModel implements AdminModel<Record> {


    public static PrimaryKeyAdapter createDefault(String pk) {
        return new OnePrimaryKeyDaoAdapter(pk);
    }

    public static interface PrimaryKeyAdapter {

		//默认orderBy
		Ar order(Ar ar);

        //以主键为条件
        Ar where(Ar ar, Object entity);

        // 移除主键对应的值
        void remove(Map<String, Object> data);
    }

    private static class MultiPrimaryKeyAdapter implements PrimaryKeyAdapter {

		private String[] idNames;

        public MultiPrimaryKeyAdapter(String[] idNames) {
			this.idNames = idNames;
		}

		@Override
		public Ar order(Ar ar) {
			for (String name : idNames) {
				ar.orderBy(name, Sort.DESC);
			}
			return ar;
		}

		@Override
		public Ar where(Ar ar, Object id) {
			assert(id instanceof String);
			String str = (String)id;
			String[] parts = str.split("__");
			if(parts.length != idNames.length) {
				throw new StatusException.ApiError(String.format("条件值 %s 和 主键 %s 不一致", str, StringUtils.join(idNames) ));
			}

			for(int i=0; i < parts.length ; ++i) {
				ar.where(idNames[i], parts[i]);
			}

			return ar;

        }

        @Override
        public void remove(Map<String, Object> data) {
            for (int i = 0; i < idNames.length; ++i) {
                data.remove(idNames[i]);
            }
        }

    }


    private static class OnePrimaryKeyDaoAdapter implements PrimaryKeyAdapter {
		private String idName;

        OnePrimaryKeyDaoAdapter(String idName) {
			this.idName = idName;

        }
		@Override
		public Ar order(Ar ar) {
			ar.orderBy(idName, Sort.DESC);
			return ar;
		}

		@Override
		public Ar where(Ar ar, Object id) {
			return ar.where(idName, id);
		}

        @Override
        public void remove(Map<String, Object> data) {
            data.remove(idName);
        }


    }

    public static PrimaryKeyAdapter getDaoId(TableMeta tableMeta, NameAdapter adapter) {
		ColumnMeta[] primaryKeys = tableMeta.getPrimaryKeys();
		if(primaryKeys==null || primaryKeys.length == 0) {
			//将第一个列设置为key
			ColumnMeta[] columns = tableMeta.getColumns();
			if(columns == null || columns.length == 0) {
				throw new RuntimeException("本表没有定义列");
			}
            return new OnePrimaryKeyDaoAdapter(adapter.getFieldName(columns[0].getName()));
		}

        if (primaryKeys.length == 1) {
            return new OnePrimaryKeyDaoAdapter(adapter.getFieldName(primaryKeys[0].getName()));
		}
		int index = 0;
		String[] idNames = new String[ primaryKeys.length];
		for (ColumnMeta meta : primaryKeys) {
			idNames[index++] = adapter.getFieldName(meta.getName());
		}

        return new MultiPrimaryKeyAdapter(idNames);
    }

    /**
     *
     */
    protected Dao dao;

    /**
     * 表
     */
    protected String table;

    /**
     * 主键适配器
     */
    private PrimaryKeyAdapter primaryKeyAdapter;

    private String ds;

	public DecorationTableService getDecorationTableService() {
		return decorationTableService;
	}

	public void setDecorationTableService(DecorationTableService decorationTableService) {
		this.decorationTableService = decorationTableService;
	}

	private DecorationTableService decorationTableService;

    public AdminDaoModel(String ds, String table, String pk) {
        this(ds, table, new OnePrimaryKeyDaoAdapter(pk));
    }

    public AdminDaoModel(String ds, String table, PrimaryKeyAdapter primaryKeyAdapter) {
        this.ds = ds;
		this.table = table;
        this.primaryKeyAdapter = primaryKeyAdapter;
	}

    private DaoListener daoListener;

	public List<Record> getList() {
        return primaryKeyAdapter.order(dao.table(table).select("*")).find();
	}
	public List<Record> getList(Map<String, Object> search) {
		Ar ar = dao.table(table);
		parseSearch(ar,search);
		return ar.find();
	}
	/**
	 * 前端传递的查询参数解析：
	 * 参数的格式为:
	 *
	 * 公共参数：
	 * {
	 *     _pageSize : 	int
	 *     _page : 		int
	 *     _order : 	String
	 *     _sort : 		String
	 * }
	 *
	 * 查询参数
	 * {
	 *     ...
	 *
	 * 	   计算符号@值
	 *
	 *     name: 		like@张三
	 *     age:  		>@5
	 *     date:		[]@["2017-10-10","2017-10-12"]			[)  (] ()  表示在区间内,后面是一个json数组
	 *
	 *
	 *
	 * }
	 *
	 */
	protected Ar parseSearch(Ar ar,Map<String, Object> search) {
		Record record = new Record(search);
		String order = Caster.to(search.get("_order"), String.class);
		String sort = Caster.to(search.get("_sort"), String.class);

		Object select = record.get("_select");
		if(select==null){
			ar.select("*");
		}else if(select instanceof  String){
			ar.select( (String)select );
		}else if(select instanceof  Iterable){
			ar.select( (Iterable<String>) select);
		}

		if(order!=null && sort!=null) {
			ar.orderBy(order, Sort.parse(sort) );
		}else {
            primaryKeyAdapter.order(ar);
		}

		parseWhere(ar, search);
		return ar;

    }

	public static void parseWhere(Ar ar,Map<String, Object> search) {
		for (Entry<String, Object> entry : search.entrySet()) {
			parseKeyAndValue(ar, entry.getKey(), entry.getValue());
		}
	}


    public static void parseKeyAndValue(Ar ar, String key, Object value) {
		if(key.contains("@")) {
			String[] parts = key.split("@");
			parseKeyAndValue(ar,parts[0],parts[1],value);
		}else {
			if(!key.startsWith("_")) {
				ar.where(key, value);
			}
		}
	}

    /**
     * 解析出来运算符
     *
     * @param ar
     * @param sign
     * @param key
     * @param value
     */
    public static void parseKeyAndValue(Ar ar, String sign, String key, Object value) {
        if ("like".equals(sign)) {
            ar.like(key, Like.BOTH, value);
        } else {
            Symbol symbol = Symbol.parse(sign);
            if (symbol != null) {
                ar.where(key, symbol, value);
            } else {
                throw new RuntimeException("不支持的操作符:" + sign);
            }

        }
    }


    private Page<Record> getPage(Map<String, Object> search, DecoTableVo decoTableVo) {
        //查询一下配置
        Ar ar = null;

        if (decoTableVo != null) {
            Set<String> columns = decoTableVo.getDisplayColumns();

            List<DecoTableVo.Link> links = decoTableVo.getLinks();
            if (links != null && links.size() > 0) {
                String[] joins = new String[links.size()];
                for (int i = 0, c = joins.length; i < c; ++i) {
                    joins[i] = links.get(i).getTable();
                }
                ar = dao.tables(AdminUtils.combine(table, joins));
                for (DecoTableVo.Link link : links) {
                    ar.join(link.getTable(), link.getOn(), link.getType());
                }
            } else {
                ar = dao.table(table);
            }

            Object select = search.get("_select");
            if (select == null) {
                if (columns != null && columns.size() > 0) {
                    search.put("_select", columns);
                }
            }
        }

        if (ar == null) {
            ar = dao.table(table);
        }

        parseSearch(ar, search);
        int pageSize = Caster.to(search.get("_pageSize"), int.class);
        if (pageSize <= 0) {
            pageSize = 30;
        }
        int page = Caster.to(search.get("_page"), int.class);
        if (page <= 0) {
            page = 1;
        }
        return ar.page(page, pageSize);
    }

    /**
     * 这个方法是针对于分页展示的
     *
     * @param search
     * @return
     */
    public Page<Record> getPage(Map<String, Object> search) {
        return getPage(search, decorationTableService.query(ds, table));
    }

    public View export(Map<String, Object> search) {
        DecoTableVo decoTableVo = decorationTableService.getTable(ds, table);
        if (decoTableVo == null) {
            throw new StatusException.ApiError("未定义表，请先定义表");
        }
        final String title = String.format("数据导出%s%s.xls", decoTableVo.getComment(), DatetimeUtils.shortDateTime());
        Page<Record> page = getPage(search, decoTableVo);
        final ExcelTable excelTable = new ExcelTable();
        excelTable.createSheet(decoTableVo.getComment());
        excelTable.addTitle(title, decoTableVo.getColumns().size());
        String[] heads = new String[decoTableVo.getColumns().size()];
        String[] keys = new String[decoTableVo.getColumns().size()];
        int index = 0;
        //计算表头
        for (DecoTableVo.DecoColumn column : decoTableVo.getColumns()) {
            heads[index] = column.getComment();
            keys[index] = column.getName();
            ++index;
        }
        excelTable.addRow(heads);

        for (Record record : page.getList()) {
            for (int i = 0, c = heads.length; i < c; ++i) {
                Object value = record.get(keys[i]);
                if (value == null) {
                    heads[i] = "";
                } else {
                    heads[i] = value.toString();
                }
            }
            excelTable.addRow(heads);
        }

        return new OutputStreamView() {

            @Override
            protected void close() throws IOException {
                excelTable.close();
            }

            @Override
            protected String getName() {
                return title;
            }

            @Override
            protected void writeTo(OutputStream outputStream) throws IOException {
                excelTable.writeTo(outputStream);
            }
        };
    }


    public int put(Object id, Map<String, Object> data) {
        if (id == null) {
            log.error("尝试修改,id为null");
            return 0;
        }
        id = this.decodeId(id);
        Ar ar = dao.ar();
        //将主键对应的值移除掉，主键不能更新

        daoListener.beforeUpdae(ar, table, id, data);
        daoListener.beforeModify(ar, table, id, data);
        this.primaryKeyAdapter.remove(data);
        if (data.size() == 0) {
            throw new StatusException.ApiError("必须有字段可以更新");
        }

        int ret = this.primaryKeyAdapter.where(dao.table(table), id).setAll(data).update();
        daoListener.onUpdate(ar, table, id, data);
		return ret;
	}

	public int add(Map<String, Object> data) {
        Ar ar = dao.ar();
        daoListener.beforeAdd(ar, table, data);
        daoListener.beforeModify(ar, table, null, data);
		int ret= dao.table(table).insert(data);
        daoListener.onAdd(ar, table, data);
		return ret;
	}


    private static final Log log = LogFactory.getLog(AdminDaoModel.class);

    public Record get(Object id) {
        if (id == null) {
            log.error("尝试获取数据，但是id null");
            return null;
        }
        id = decodeId(id);

        return this.primaryKeyAdapter.where(dao.table(table), id).get();
	}

    private Object decodeId(Object id) {
        if (id instanceof String) {
            String str = (String) id;
            str = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
            try {
                return URLDecoder.decode(str, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new StatusException.BadRequest();
            }
        }
        return id;
    }

    public int del(Object id) {
        if (id == null) {
            log.error("尝试删除数据，但是id null");
            return 0;
        }
        id = decodeId(id);
        Ar ar = dao.ar();
        daoListener.beforeDel(ar, table, id);
        int ret = this.primaryKeyAdapter.where(dao.table(table), id).delete();
        daoListener.onDel(ar, table, id);
		return ret;
	}

    public int putMulti(final List<Object> ids, final Map<String, Object> data) {
        try {
            final MutableInt result = new MutableInt(0);
            ZoomDao.runTrans(new Runnable() {
                @Override
                public void run() {
                    for (Object id : ids) {
                        result.add(put(id, data));
                    }
                }
            });
            return result.getValue();
        } catch (Throwable throwable) {
			throw Classes.makeThrow(throwable);
        }
    }


    public int delMulti(final Iterable<Object> ids) {
        try {
            final MutableInt result = new MutableInt(0);
            ZoomDao.runTrans(new Runnable() {
                @Override
                public void run() {
                    for (Object id : ids) {
                        result.add(del(id));
                    }
                }
            });
            return result.getValue();
        } catch (Throwable throwable) {
			throw Classes.makeThrow(throwable);
        }
    }

    public Dao getDao() {
		return dao;
	}

	public void setDao(Dao dao) {
		this.dao = dao;
	}


    public PrimaryKeyAdapter getPrimaryKeyAdapter() {
        return primaryKeyAdapter;
    }

    public void setPrimaryKeyAdapter(PrimaryKeyAdapter primaryKeyAdapter) {
        this.primaryKeyAdapter = primaryKeyAdapter;
	}
	public DaoListener getDaoListener() {
		return daoListener;
	}
	public void setDaoListener(DaoListener daoListener) {
		this.daoListener = daoListener;
	}
	public String getDs() {
		return ds;
	}
	public void setDs(String ds) {
		this.ds = ds;
	}

}
